import requests
from sqlalchemy.exc import OperationalError
from sqlalchemy import func
from urllib.parse import urlencode
import json
import math
import time
import os
from logger import singer_logger, song_logger, create_logger
from models.tables import SingerModel, MusicModel
from models import db
import config
from requests import urllib3
urllib3.disable_warnings()


class Spider(object):
    def __init__(self):
        self.total_singer = 0
        self.singer_list = []
        self.error_singer = []

    def get_singer_by_page(self, page):
        singer_logger.info('start---' + str(page))
        callback_key = 'getUCGI6682181276362078'
        url = 'https://u.y.qq.com/cgi-bin/musicu.fcg?'
        params = {
            "g_tk": 5381,
            "loginUin": 0,
            "hostUin": 0,
            "format": "json",
            "inCharset": "utf8",
            "outCharset": "utf8",
            "notice": 0,
            "platform": 'yqq',
            "needNewCode": 0,
            "callback": callback_key,
            "jsonpCallback": callback_key,
            "data": json.dumps({
                "comm": {
                    "ct": 24,
                    "cv": 10000
                },
                "singerList": {
                    "module": "Music.SingerListServer",
                    "method": "get_singer_list",
                    "param": {
                        "area": -100,
                        "sex": -100,
                        "genre": -100,
                        "index": -100,
                        "sin": (page - 1) * 80,
                        "cur_page": page
                    }
                }
            })
        }
        params_str = urlencode(params)
        url += params_str
        url = url.replace('+', '')
        resp = requests.get(url=url, verify=False)
        try:
            resp_json_text = resp.text.replace(callback_key + '(', '')
            resp_json_text = resp_json_text[:-1]
            resp_json = json.loads(resp_json_text)
            resp_data = resp_json.get('singerList', dict()).get('data', dict())
            singer_list = resp_data.get('singerlist', list())
            total = resp_data.get('total', 0)
        except Exception:
            singer_logger.error('Exception get_singer_list---' + page)
            singer_list = list()
            total = 0

        singer_logger.info('end--' + str(page) + '--len--' + str(len(singer_list)))
        if self.total_singer == 0 and total:
            self.total_singer = total
        return singer_list

    def get_all_singer(self):
        singer_logger.info('start get singer list')
        self.get_singer_by_page(1)
        singer_logger.info('total_singer---' + str(self.total_singer))
        if self.total_singer == 0:
            singer_logger.error('get total singer error')
            return
        total_page = int(math.ceil(self.total_singer / 80))
        singer_logger.error('total_page---' + str(total_page))
        current_page = 1
        error_page = []
        while True:
            time.sleep(0.2)
            current_list = self.get_singer_by_page(current_page)
            if len(current_list) == 0:
                error_page.append(current_page)
            self.singer_list += current_list
            current_page += 1
            if current_page > total_page:
                break
        singer_logger.info('get singer list end---' + str(len(self.singer_list)))
        singer_logger.info('get singer list error page---' + str(error_page))
        if len(error_page) > 0:
            self.get_error_singer_page(error_page)
        singer_logger.info('get singer all error page end---' + str(len(self.singer_list)))
        self.save_all_singer_to_tmp()
        self.save_all_singer_to_db()

    def get_error_singer_page(self, error_list):
        singer_logger.info('start singer error page' + str(error_list))
        index = 0
        error_page = []
        while True:
            current_list = self.get_singer_by_page(error_list[index])
            if len(current_list) == 0:
                error_page.append(error_list[index])
            self.singer_list += current_list
            index += 1
            if index >= len(error_list):
                break
        if len(error_page) > 0:
            self.get_error_singer_page(error_page)

    def save_all_singer_to_db(self):
        singer_logger.info('start save singer to db')
        index = 0
        singer_model_list = []
        while True:
            current_singer = self.singer_list[index]
            singer = SingerModel(singer_name=current_singer.get('singer_name', ''),
                                 singer_mid=current_singer.get('singer_mid', ''),
                                 singer_pic=current_singer.get('singer_pic', ''),
                                 singer_country=current_singer.get('country', ''),
                                 )
            singer_model_list.append(singer)
            index += 1
            if index >= len(self.singer_list):
                break
        db.add_all(singer_model_list)
        try:
            db.commit()
        except OperationalError:
            db.rollback()

        singer_logger.info('end save singer to db')

    def save_all_singer_to_tmp(self):
        file_name = os.path.join(config.APP_ROOT, 'tmp/singerlist' + self.get_time_str() + '.js')
        if not os.path.exists(file_name):
            fp = open(file_name, 'w+')
            fp.write('var singer_list = ')
            fp.write(str(self.singer_list))
            fp.close()

    def get_all_song_url(self, start=1, end=50000):
        song_url_logger = create_logger('song_url--' + str(start) + '---' + str(end))
        max_count = db.query(func.count('*')).select_from(MusicModel).scalar()
        if end:
            max_count = end
        count = start
        while True:
            song_url_logger.info('start get song url id==' + str(count))
            music = db.query(MusicModel).filter_by(id=count).one()
            song_url = ''
            if music and music.song_mid:
                song_url = self.get_song_key(music.song_mid, song_url_logger)
                music.song_url = song_url
                self.do_db_commit()
            if not song_url:
                song_url_logger.error('error get song url id==' + str(count))

            song_url_logger.info('end get song url id==' + str(count))
            count += 1
            if count > max_count:
                break

    def get_all_song(self, start=0):
        song_logger.info('start get all song')
        all_singers = db.query(SingerModel).all()
        count = start
        error_singer_list = []
        while True:
            singer = all_singers[count]
            song_list = self.get_singer_song_list(singer.singer_mid, singer.singer_name, count)
            if len(song_list) == 0:
                error_singer_list.append(singer.singer_mid)
            count += 1
            if count >= len(all_singers):
                break
        song_logger.info('end get all song')
        song_logger.error('error_singer_list--' + str(error_singer_list))

    def get_singer_song_list(self, singer_mid, singer_name, count):
        song_logger.info(str(count) + '-start get song list--singer_mid==' + singer_mid + '--singer_name=='
                         + singer_name)
        url = 'https://c.y.qq.com/v8/fcg-bin/fcg_v8_singer_track_cp.fcg'
        params = {
            'g_tk': 5381,
            'jsonpCallback': 'MusicJsonCallbacksinger_track',
            'loginUin': 0,
            'hostUin': 0,
            'format': 'jsonp',
            'inCharset': 'utf8',
            'outCharset': 'utf-8',
            'notice': 0,
            'platform': 'yqq',
            'needNewCode': 0,
            'singermid': singer_mid,
            'order': 'listen',
            'begin': 0,
            'num': 1000,
            'songstatus': 1
        }
        resp = requests.get(url=url, params=params, verify=False)
        try:
            resp_json = resp.text.replace('MusicJsonCallbacksinger_track(', '')
            resp_json = resp_json[:-1]
            resp_json = json.loads(resp_json)
        except Exception:
            song_logger.error(str(count) + '-get song list error singer_mid==' + singer_mid)
            resp_json = {}
        song_list = []
        if resp_json.get('code', None) == 0:
            song_list += resp_json.get('data', dict()).get('list', [])
        song_list = [_.get('musicData', dict()) for _ in song_list]
        for _ in song_list:
            _['state'] = self.check_music_disable(_)
            _['singer_mid'] = singer_mid
            _['singer_name'] = singer_name
        song_logger.info(str(count) + '-end get song list--singer_mid==' + singer_mid + '--count=='
                         + str(len(song_list)))
        self.save_songs_to_db(song_list)
        return song_list

    @staticmethod
    def save_songs_to_db(song_list):
        song_db_list = []
        for _ in song_list:
            song = MusicModel(song_mid=_.get('songmid'),
                              song_name=_.get('songname'),
                              song_info=json.dumps(_),
                              album_mid=_.get('albummid'),
                              album_name=_.get('albumname'),
                              singer_mid=_.get('singer_mid'),
                              singer_name=_.get('singer_name'),
                              song_url='',
                              my_song_url='',
                              state=_.get('state')
                              )
            song_db_list.append(song)
        db.add_all(song_db_list)
        try:
            db.commit()
        except OperationalError:
            db.rollback()

    @staticmethod
    def get_song_key(song_mid, song_url_logger):
        callback_key = 'getplaysongvkey725539600059133'
        url = "https://u.y.qq.com/cgi-bin/musicu.fcg?"
        params = {
            "g_tk": 5381,
            "loginUin": 0,
            "hostUin": 0,
            "format": "json",
            "inCharset": "utf8",
            "outCharset": "utf8",
            "notice": 0,
            "platform": 'yqq',
            "needNewCode": 0,
            "callback": callback_key,
            "jsonpCallback": callback_key,
            "data": json.dumps({
                "req": {"module": "CDN.SrfCdnDispatchServer", "method": "GetCdnDispatch",
                        "param": {"guid": "6501846271", "calltype": 0, "userip": ""}},
                "req_0": {
                    "module": "vkey.GetVkeyServer",
                    "method": "CgiGetVkey",
                    "param": {
                        "guid": "6501846271",
                        "songmid": [song_mid],
                        "songtype": [0],
                        "uin": "0",
                        "loginflag": 1,
                        "platform": "20"
                    }
                },
                "comm": {
                    "uin": 0,
                    "format": "json",
                    "ct": 20,
                    "cv": 0
                }
            })
        }
        params_str = urlencode(params)
        url += params_str
        url = url.replace('+', '')
        song_url = []
        sip = []
        try:
            resp = requests.get(url=url, verify=False)
            resp_json_text = resp.text.replace(callback_key + '(', '')
            resp_json_text = resp_json_text[:-1]
            resp_json = json.loads(resp_json_text)
            if resp_json.get('code', None) == 0:
                song_url = resp_json.get('req_0', None).get('data', None).get('midurlinfo', [])
                song_url = [_.get('purl', None) for _ in song_url]
                sip = resp_json.get('req', None).get('data', None).get('sip', [])
        except Exception as e:
            print('Exception get_song_key')
            resp_json = dict()
            song_url_logger.error(str(e))
        # print('resp_json', resp_json)
        real_url = ''
        if len(song_url) > 0 and len(sip) > 0:
            real_url = sip[0] + song_url[0]
        return real_url

    @staticmethod
    def format_music_action(switch):
        switch_list = list(bin(switch).replace('0b', ''))
        switch_list.pop()
        switch_list.reverse()
        o = ["play_lq", "play_hq", "play_sq", "down_lq",
             "down_hq", "down_sq", "soso", "fav", "share",
             "bgm", "ring", "sing", "radio", "try", "give"]
        action = {}
        for _ in range(len(o)):
            action[o[_]] = 0
            if len(switch_list) > _:
                action[o[_]] = int(switch_list[_])
        return action

    def check_music_disable(self, music_data):
        action = self.format_music_action(music_data['switch'])
        action['play'] = 0
        preview = music_data.get('preview', {})
        pay = music_data.get('pay', {})
        if action.get('play_lq', 0) or action.get('play_hq', 0) or action.get('play_sq', 0):
            action['play'] = 1
        try_play = 0
        if action.get('try', 0) == 1 and preview.get('trysize', 0) == 1:
            try_play = 1
        disabled = 0
        if not action.get('play', 0) \
                and not pay.get('payplay', 0) \
                and not pay.get('paydownload', 0) \
                and try_play == 0:
            disabled = 1
        return disabled == 0

    @staticmethod
    def get_time_str():
        return str(int(time.time()))

    @staticmethod
    def do_db_commit():
        try:
            db.commit()
        except OperationalError:
            db.rollback()


def check_disable(music_data):
    action = format_music_action(music_data['switch'])
    action['play'] = 0
    preview = music_data.get('preview', {})
    pay = music_data.get('pay', {})
    if action.get('play_lq', 0) or action.get('play_hq', 0) or action.get('play_sq', 0):
        action['play'] = 1
    try_play = 0
    if action.get('try', 0) == 1 and preview.get('trysize', 0) == 1:
        try_play = 1
    disabled = 0
    if not action.get('play', 0) \
            and not pay.get('payplay', 0) \
            and not pay.get('paydownload', 0) \
            and try_play == 0:
        disabled = 1
    return disabled == 0 and music_data.get('albummid', '')


def format_music_action(switch):
    switch_list = list(bin(switch).replace('0b', ''))
    switch_list.pop()
    switch_list.reverse()
    o = ["play_lq", "play_hq", "play_sq", "down_lq",
         "down_hq", "down_sq", "soso", "fav", "share",
         "bgm", "ring", "sing", "radio", "try", "give"]
    action = {}
    for _ in range(len(o)):
        action[o[_]] = int(switch_list[_])
    return action


def get_singer_song_list(singer_mid):
    url = 'https://c.y.qq.com/v8/fcg-bin/fcg_v8_singer_track_cp.fcg'
    params = {
        'g_tk': 5381,
        'jsonpCallback': 'MusicJsonCallbacksinger_track',
        'loginUin': 0,
        'hostUin': 0,
        'format': 'jsonp',
        'inCharset': 'utf8',
        'outCharset': 'utf-8',
        'notice': 0,
        'platform': 'yqq',
        'needNewCode': 0,
        'singermid': singer_mid,
        'order': 'listen',
        'begin': 0,
        'num': 1000,
        'songstatus': 1
    }
    resp = requests.get(url=url, params=params, verify=False)
    try:
        resp_json = resp.text.replace('MusicJsonCallbacksinger_track(', '')
        resp_json = resp_json[:-1]
        resp_json = json.loads(resp_json)
    except Exception:
        resp_json = {}
    song_list = []
    if resp_json.get('code', None) == 0:
        song_list += resp_json.get('data', dict()).get('list', [])
    song_list = [_.get('musicData', dict()) for _ in song_list]
    print(len(song_list))
    able_song_list = filter(check_disable, song_list)
    able_song_list = list(able_song_list)
    print('able_song_list', len(able_song_list))
    # print(able_song_list[-1])


def get_all_singer(page):
    pass

